﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Reflection;
using System.Data.Linq.Mapping;
using System.Text;
using System.Globalization;

namespace BulletOnRails4NET.Models
{
    public class RuleViolationException : ApplicationException
    {
        public List<RuleViolation> RulesViolations { get; set; }
        public RuleViolationException(string errorMessage, List<RuleViolation> rules_violations)
            : base(errorMessage)
        {
            RulesViolations = rules_violations;
        }
    }
    public class RuleViolation
    {
        public string ErrorMessage { get; private set; }
        public string PropertyName { get; private set; }
        public string PropertyValue { get; private set; }
        public RuleViolation(string errorMessage)
        {
            ErrorMessage = errorMessage;
        }
        public RuleViolation(string errorMessage, string propertyName, string prop_value)
        {
            ErrorMessage = errorMessage;
            PropertyName = propertyName;
            PropertyValue = prop_value;
        }
        public static RuleViolation CreateRequiredFieldViolation(string field_name)
        {
            return new RuleViolation(field_name.HumanizeString() + " is required", field_name, "");
        }

        public static RuleViolation CreateTooLongFieldViolation(string field_name, int max_len, string prop_value)
        {
            return new RuleViolation(field_name.HumanizeString() + " too long (Max. length is " + max_len.ToString() + ")", field_name, prop_value);
        }
        public static RuleViolation CreateIncorrectDateFormatViolation(string field_name, string date_str)
        {
            //TODO: add formats for Time too
            return new RuleViolation(field_name.HumanizeString() + ": " + date_str + " is an incorrect date format, must be " + CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern, field_name, date_str);
        }
        public static RuleViolation CreateIncorrectNumberFormatViolation(string field_name, string number_str)
        {
            //TODO: add formats for Time too
            return new RuleViolation(field_name.HumanizeString() + ": " + number_str + " is an incorrect number format", field_name, number_str);
        }



        public static List<RuleViolation> CheckBasicDBCobnstraints(object obj, PropertyInfo prop, ColumnAttribute ca, object prop_value_to_assign, bool save_value_to_property)
        {
            List<RuleViolation> violations = new List<RuleViolation>();
            string dbtype = ca.DbType;
            bool valid_value = true;
            if (!ca.IsDbGenerated)
            {
                //Check if it is text
                if ((prop.PropertyType == typeof(System.Nullable<char>)) ||
                    (prop.PropertyType == typeof(char)))
                {
                    if (prop_value_to_assign != null)
                    {
                        if (prop_value_to_assign.GetType() == typeof(string))
                        {
                            string str_value = prop_value_to_assign.ToString();
                            if (str_value.Length > 0)
                                prop_value_to_assign = str_value[0];
                            else
                                prop_value_to_assign = null;
                        }
                    }
                    if (!ca.CanBeNull)
                    {

                        if (prop_value_to_assign == null || Char.IsWhiteSpace((char)prop_value_to_assign))
                        {
                            violations.Add(CreateRequiredFieldViolation(prop.Name));
                            valid_value = false;
                        }
                    }
                    if (valid_value && save_value_to_property)
                        prop.SetValue(obj, prop_value_to_assign, null);

                }
                else if (dbtype.Contains("VarChar") ||
                    dbtype.StartsWith("NChar") ||
                    dbtype.StartsWith("Char("))
                {

                    string property_str_value;
                    if (prop_value_to_assign == null)
                        property_str_value = null;
                    else
                        property_str_value = prop_value_to_assign.ToString();
                    bool property_is_empty = String.IsNullOrEmpty(property_str_value);
                    //Check if it is required or not
                    if (!ca.CanBeNull)
                    {
                        if (property_is_empty)
                        {
                            violations.Add(CreateRequiredFieldViolation(prop.Name));
                            property_is_empty = true;
                            valid_value = false;
                        }
                    }
                    //check the lenght
                    if (!property_is_empty)
                    {

                        int dblenint = 0;
                        int index1 = dbtype.IndexOf("(");
                        int index2 = dbtype.IndexOf(")");
                        string dblen = dbtype.Substring(index1 + 1, index2 - index1 - 1);
                        int.TryParse(dblen, out dblenint);
                        if (property_str_value.Length > dblenint)
                        {
                            violations.Add(CreateTooLongFieldViolation(prop.Name, dblenint, property_str_value));
                            valid_value = false;
                        }
                    }
                    if (valid_value && save_value_to_property)
                        prop.SetValue(obj, property_str_value, null);
                }
                else
                {
                    //If the text value is blank then it is safe to set the value
                    // to null because we know that the property is not type text
                    if (prop_value_to_assign != null && prop_value_to_assign.ToString().IsBlank())
                        prop_value_to_assign = null;

                    bool val_to_assign_is_empty = (prop_value_to_assign == null);
                    bool value_assigned = false;
                    //Check if it is required or not
                    if (!ca.CanBeNull)
                    {
                        if (val_to_assign_is_empty)
                        {
                            violations.Add(CreateRequiredFieldViolation(prop.Name));
                            val_to_assign_is_empty = true;
                            valid_value = false;
                        }
                    }
                    //Handle in case the property is a Date type
                    if (!val_to_assign_is_empty)
                    {
                        if (prop.PropertyType == typeof(System.Nullable<System.DateTime>) || prop.PropertyType == typeof(System.DateTime))
                        {
                            //Check the format of the date to assign
                            // Assume is a string if it is not a date
                            if (prop_value_to_assign.GetType() != typeof(System.DateTime))
                            {
                                DateTime dt_parsed;
                                if (!DateTime.TryParse(prop_value_to_assign.ToString(), out dt_parsed))
                                {
                                    violations.Add(CreateIncorrectDateFormatViolation(prop.Name, prop_value_to_assign.ToString()));
                                    valid_value = false;
                                }
                                else
                                {
                                    if (save_value_to_property)
                                    {
                                        prop.SetValue(obj, dt_parsed, null);
                                        value_assigned = true;
                                    }
                                }
                            }
                            else if (save_value_to_property)
                            {
                                prop.SetValue(obj, prop_value_to_assign, null);
                                value_assigned = true;
                            }
                        }
                        else if (((prop.PropertyType == typeof(System.Nullable<int>)) ||
                     (prop.PropertyType == typeof(int))) ||
                         ((prop.PropertyType == typeof(System.Nullable<float>)) ||
                     (prop.PropertyType == typeof(float))) ||
                         ((prop.PropertyType == typeof(System.Nullable<double>)) ||
                     (prop.PropertyType == typeof(double))) ||
                         ((prop.PropertyType == typeof(System.Nullable<decimal>)) ||
                     (prop.PropertyType == typeof(decimal))))
                        {
                            if ((prop_value_to_assign.GetType() != typeof(int)) &&
                                (prop_value_to_assign.GetType() != typeof(float)) &&
                                (prop_value_to_assign.GetType() != typeof(double)) &&
                                (prop_value_to_assign.GetType() != typeof(decimal)))
                            {
                                double number_parsed = 0;
                                if (!Double.TryParse(prop_value_to_assign.ToString(), out number_parsed))
                                {
                                    violations.Add(CreateIncorrectNumberFormatViolation(prop.Name, prop_value_to_assign.ToString()));
                                    valid_value = false;
                                }
                                else if (save_value_to_property)
                                {
                                    //Check for nullables
                                    if (prop.PropertyType == typeof(System.Nullable<int>))
                                    {
                                        prop.SetValue(obj, new Nullable<int>((int)number_parsed), null);
                                    }
                                    else if (prop.PropertyType == typeof(System.Nullable<float>))
                                    {
                                        prop.SetValue(obj, new Nullable<float>((float)number_parsed), null);
                                    }
                                    else if (prop.PropertyType == typeof(System.Nullable<double>))
                                    {
                                        prop.SetValue(obj, new Nullable<double>(number_parsed), null);
                                    }
                                    else if (prop.PropertyType == typeof(System.Nullable<decimal>))
                                    {
                                        prop.SetValue(obj, new Nullable<decimal>((decimal)number_parsed), null);
                                    }
                                    else
                                    {
                                        object final_value = Convert.ChangeType(number_parsed, prop.PropertyType);
                                        prop.SetValue(obj, final_value, null);
                                    }
                                    value_assigned = true;
                                }
                            }
                            else if (save_value_to_property)
                            {
                                prop.SetValue(obj, prop_value_to_assign, null);
                                value_assigned = true;
                            }

                        }
                    }

                    //Save the value if was not saved by the above format handlers
                    if (!value_assigned && save_value_to_property && valid_value)
                    {
                        prop.SetValue(obj, prop_value_to_assign, null);
                        value_assigned = true;
                    }
                }
            }
            return violations;

        }

        //Parts of this mthod were taken from http://www.codeproject.com/KB/cs/LinqColumnAttributeTricks.aspx?display=Print
        // Checks that fields required fields in the database (not null flagged) are present
        // * checks that values are numbers for numeric fields
        // * Checks that string values are not too big for the db field
        public static List<RuleViolation> CheckBasicDBCobnstraints(object obj)
        {
            List<RuleViolation> violations = new List<RuleViolation>();
            //bool is_valid = true;
            Type type = obj.GetType();
            object[] info;
            object prop_value;
            PropertyInfo[] properties = type.GetProperties();
            foreach (PropertyInfo prop in properties)
            {
                //Get the Linq realted info
                info = prop.GetCustomAttributes(typeof(ColumnAttribute), true);
                // Check if it is a Linq column property
                if (info.Length == 1)
                {
                    ColumnAttribute ca = (ColumnAttribute)info[0];
                    prop_value = prop.GetValue(obj, null);
                    violations.AddRange(CheckBasicDBCobnstraints(obj, prop, ca, prop_value, false));
                }
            }
            return violations;
        }


    }
}
